package org.jw.testemunhopublico.repository.dao.impl;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaQuery;

import org.jw.testemunhopublico.repository.dao.GenericDaoIF;
import org.jw.testemunhopublico.util.Constantes;

@SuppressWarnings({ "rawtypes", "unchecked" })
abstract class GenericDao<T> implements Serializable, GenericDaoIF<T> {

	private static final long serialVersionUID = 4437514155333967775L;

	private static final EntityManagerFactory factory = Persistence
			.createEntityManagerFactory(Constantes.APPLICATION_PERSISTENCE_UNIT);
	
	protected EntityManager manager;

	protected Class<T> entityClass;

	public GenericDao() {
		this.entityClass = getTypeClass();
	}

	public void beginTransaction() {
		manager = factory.createEntityManager();
		manager.getTransaction().begin();
	}

	public void closeTransaction() {
		manager.close();
	}

	public void commit() {
		manager.getTransaction().commit();
	}

	public void commitAndCloseTransaction() {
		commit();
		closeTransaction();
	}

	public void delete(T entity) {
		beginTransaction();
		onlyDelete(entity);
		commitAndCloseTransaction();
	}

	public T find(int entityID) {
		beginTransaction();
		T result = onlyFind(entityID);
		closeTransaction();
		return result;
	}

	public List<T> findAll() {
		beginTransaction();
		List<T> result = onlyFindAll();
		closeTransaction();
		return result;
	}

	protected List<T> findByParameters(String namedQuery,
			Map<String, Object> parameters) {
		beginTransaction();
		List<T> result = onlyFindByParameters(namedQuery, parameters);
		closeTransaction();
		return result;
	}

	public T findReferenceOnly(int entityID) {
		beginTransaction();
		T result = onlyFindReferenceOnly(entityID);
		closeTransaction();
		return result;
	}

	public void flush() {
		manager.flush();
	}

	protected Class<T> getTypeClass() {
		Class<T> clazz = (Class<T>) ((ParameterizedType) this.getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
		return clazz;
	}

	public void joinTransaction() {
		manager = factory.createEntityManager();
		manager.joinTransaction();
	}

	public void onlyDelete(T entity) {
		T entityToBeRemoved = manager.merge(entity);
		manager.remove(entityToBeRemoved);
	}

	public T onlyFind(int entityID) {
		return manager.find(entityClass, entityID);
	}

	public List<T> onlyFindAll() {
		CriteriaQuery cq = manager.getCriteriaBuilder().createQuery();
		cq.select(cq.from(entityClass));
		return manager.createQuery(cq).getResultList();
	}

	protected List<T> onlyFindByParameters(String namedQuery,
			Map<String, Object> parameters) {
		Query query = manager.createNamedQuery(namedQuery);

		if (parameters != null && !parameters.isEmpty())
			populateQueryParameters(query, parameters);

		return (List<T>) query.getResultList();

	}

	public T onlyFindReferenceOnly(int entityID) {
		return manager.getReference(entityClass, entityID);
	}

	private void populateQueryParameters(Query query,
			Map<String, Object> parameters) {
		for (Entry<String, Object> entry : parameters.entrySet()) {
			query.setParameter(entry.getKey(), entry.getValue());
		}
	}

	public void rollback() {
		manager.getTransaction().rollback();
	}

	public void save(T entity) {
		beginTransaction();
		onlySave(entity);
		commitAndCloseTransaction();
	}
	
	public void onlySave(T entity) {
		manager.persist(entity);
	}

	public T update(T entity) {
		beginTransaction();
		T result = update(entity);
		commitAndCloseTransaction();
		return result;
	}
	
	public T onlyUpdate(T entity) {
		return manager.merge(entity);
	}

}